/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package mopso;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import mopso.DominanceOperators.Dominance;

/**
 *
 * @author Guest
 */
public class PerformanceMetrics {

    private DominanceOperators dominanceOperator;
    private Repository repositoryOperator;

    public PerformanceMetrics() {
        this.dominanceOperator = new DominanceOperators();
        this.repositoryOperator = new Repository();
    }

    public double calculateGenerationalDistance(ArrayList<Particle> swarm, ArrayList<Particle> externalArchive, Problem problem) {
        ArrayList<Particle> nonDominatedSwarm;
        double[] maximumValue;
        double[] minimumValue;
        double[][] normalizedFront;
        double[][] normalizedTrueParetoFront;
        double generationalDistance;
        double sum;

        nonDominatedSwarm = this.nonDominated(swarm, problem);

        maximumValue = this.getMaximumValue(externalArchive, problem);
        minimumValue = this.getMinimumValue(externalArchive, problem);

        normalizedFront = this.getNormalizedFront(nonDominatedSwarm, maximumValue, minimumValue, problem);
        normalizedTrueParetoFront = this.getNormalizedFront(externalArchive, maximumValue, minimumValue, problem);

        sum = 0.0;
        for (int i = 0; i < normalizedFront.length; i++) {
            sum += Math.pow(this.distanceToClosedPoint(normalizedFront[i],
                    normalizedTrueParetoFront),
                    2.0);
        }


        // STEP 4. Obtain the sqrt of the sum
        sum = Math.sqrt(sum);

        // STEP 5. Divide the sum by the maximum number of points of the front
        generationalDistance = sum / normalizedFront.length;

        return generationalDistance;
    }

    public double calculateSpread(ArrayList<Particle> swarm, ArrayList<Particle> externalArchive, Problem problem) {
        ArrayList<Particle> nonDominatedSwarm;
        double[] maximumValue;
        double[] minimumValue;
        double[][] normalizedFront;
        double[][] normalizedTrueParetoFront;

        nonDominatedSwarm = this.nonDominated(swarm, problem);

        maximumValue = this.getMaximumValue(externalArchive, problem);
        minimumValue = this.getMinimumValue(externalArchive, problem);

        normalizedFront = this.getNormalizedFront(nonDominatedSwarm, maximumValue, minimumValue, problem);
        normalizedTrueParetoFront = this.getNormalizedFront(externalArchive, maximumValue, minimumValue, problem);

        Arrays.sort(normalizedFront, new LexicoGraphicalComparator());
        Arrays.sort(normalizedTrueParetoFront, new LexicoGraphicalComparator());

        int numberOfPoints = normalizedFront.length;
//    int numberOfTruePoints = normalizedParetoFront.length;

        // STEP 4. Compute df and dl (See specifications in Deb's description of
        // the metric)
        double df = this.distance(normalizedFront[0], normalizedTrueParetoFront[0]);
        double dl = this.distance(normalizedFront[normalizedFront.length - 1],
                normalizedTrueParetoFront[normalizedTrueParetoFront.length - 1]);

        double mean = 0.0;
        double diversitySum = df + dl;

        // STEP 5. Calculate the mean of distances between points i and (i - 1).
        // (the poins are in lexicografical order)
        for (int i = 0; i < (normalizedFront.length - 1); i++) {
            mean += this.distance(normalizedFront[i], normalizedFront[i + 1]);
        } // for

        mean = mean / (double) (numberOfPoints - 1);

        // STEP 6. If there are more than a single point, continue computing the
        // metric. In other case, return the worse value (1.0, see metric's
        // description).
        if (numberOfPoints > 1) {
            for (int i = 0; i < (numberOfPoints - 1); i++) {
                diversitySum += Math.abs(this.distance(normalizedFront[i],
                        normalizedFront[i + 1]) - mean);
            } // for

            return diversitySum / (df + dl + (numberOfPoints - 1) * mean);
        } else {
            return 1.0;
        }
    } // spread

    public double calculateSpacing(ArrayList<Particle> solutions, Problem problem) {

        int nSolutions = solutions.size();
        double[] distances = new double[nSolutions];
        double meanDistance;
        double sum = 0.0;
        double[] differenceDistances = new double[nSolutions];
        double sumDifferences = 0.0;

        for (int i = 0; i < nSolutions; i++) {
            distances[i] = this.calculateMinDistance(solutions.get(i), i, solutions);
            sum += distances[i];
        }

        meanDistance = sum / (double) nSolutions;

        for (int i = 0; i < nSolutions; i++) {
            differenceDistances[i] = Math.pow(meanDistance - distances[i], 2);
            sumDifferences += differenceDistances[i];
        }

        return Math.sqrt((1 / ((double) nSolutions - 1)) * sumDifferences);
    }

    public double calculateSpacing_CSS(ArrayList<Particle_CSS> solutions, Problem problem) {

        int nSolutions = solutions.size();
        double[] distances = new double[nSolutions];
        double meanDistance;
        double sum = 0.0;
        double[] differenceDistances = new double[nSolutions];
        double sumDifferences = 0.0;

        for (int i = 0; i < nSolutions; i++) {
            distances[i] = this.calculateMinDistance_CSS(solutions.get(i), i, solutions);
            sum += distances[i];
        }

        meanDistance = sum / (double) nSolutions;

        for (int i = 0; i < nSolutions; i++) {
            differenceDistances[i] = Math.pow(meanDistance - distances[i], 2);
            sumDifferences += differenceDistances[i];
        }

        return Math.sqrt((1 / ((double) nSolutions - 1)) * sumDifferences);
    }

    public double calculateMaximumSpread(ArrayList<Particle> solutions, Problem problem) {

        double sumDistances = 0.0;


        for (int i = 0; i < problem.getNObjectives(); i++) {
            this.calculateMaxValue(solutions, i);
            sumDistances += Math.pow(this.calculateMaxValue(solutions, i) - this.calculateMinValue(solutions, i), 2);
        }

        return Math.sqrt(sumDistances);
    }

    public double calculateMaximumSpread_CSS(ArrayList<Particle_CSS> solutions, Problem problem) {

        double sumDistances = 0.0;


        for (int i = 0; i < problem.getNObjectives(); i++) {
//            this.calculateMaxValue(solutions, i);
            sumDistances += Math.pow(this.calculateMaxValue_CSS(solutions, i) - this.calculateMinValue_CSS(solutions, i), 2);
        }

        return Math.sqrt(sumDistances);
    }

    private double calculateMaxValue(ArrayList<Particle> solutions, int objectiveIndex) {
        double maxValue = 0.0;

        for (int i = 0; i < solutions.size(); i++) {
            if (solutions.get(i).getParticleData().getFitness()[objectiveIndex] > maxValue) {
                maxValue = solutions.get(i).getParticleData().getFitness()[objectiveIndex];
            }
        }

        return maxValue;
    }

    private double calculateMinValue(ArrayList<Particle> solutions, int objectiveIndex) {
        double minValue = Double.MAX_VALUE;

        for (int i = 0; i < solutions.size(); i++) {
            if (solutions.get(i).getParticleData().getFitness()[objectiveIndex] < minValue) {
                minValue = solutions.get(i).getParticleData().getFitness()[objectiveIndex];
            }
        }

        return minValue;
    }

    private double calculateMaxValue_CSS(ArrayList<Particle_CSS> solutions, int objectiveIndex) {
        double maxValue = 0.0;

        for (int i = 0; i < solutions.size(); i++) {
            if (solutions.get(i).getParticleData().getFitness()[objectiveIndex] > maxValue) {
                maxValue = solutions.get(i).getParticleData().getFitness()[objectiveIndex];
            }
        }

        return maxValue;
    }

    private double calculateMinValue_CSS(ArrayList<Particle_CSS> solutions, int objectiveIndex) {
        double minValue = Double.MAX_VALUE;

        for (int i = 0; i < solutions.size(); i++) {
            if (solutions.get(i).getParticleData().getFitness()[objectiveIndex] < minValue) {
                minValue = solutions.get(i).getParticleData().getFitness()[objectiveIndex];
            }
        }

        return minValue;
    }

    public double calculateCoverage(ArrayList<Particle> solutions1, ArrayList<Particle> solutions2, Problem problem) {
        double coverageValue = 0.0;
        int nDominatedSolutions = 0;

        for (int i = 0; i < solutions2.size(); i++) {

            for (int j = 0; j < solutions1.size(); j++) {

                if (dominanceOperator.verifyDominace(solutions2.get(i), solutions1.get(j), problem) == Dominance.DOMINATED) {
                    nDominatedSolutions++;
                    break;
                }
            }
        }

        coverageValue = (double) nDominatedSolutions / (double) solutions2.size();
        return coverageValue;
    }

    public double calculateCoverage_CSS1(ArrayList<Particle_CSS> solutions1, ArrayList<Particle> solutions2, Problem problem) {
        double coverageValue = 0.0;
        int nDominatedSolutions = 0;

        for (int i = 0; i < solutions2.size(); i++) {

            for (int j = 0; j < solutions1.size(); j++) {

                if (dominanceOperator.verifyDominace(solutions2.get(i).getParticleData(), solutions1.get(j).getParticleData(), problem) == Dominance.DOMINATED) {
                    nDominatedSolutions++;
                    break;
                }
            }
        }

        coverageValue = (double) nDominatedSolutions / (double) solutions2.size();
        return coverageValue;
    }

    public double calculateCoverage_CSS2(ArrayList<Particle> solutions1, ArrayList<Particle_CSS> solutions2, Problem problem) {
        double coverageValue = 0.0;
        int nDominatedSolutions = 0;

        for (int i = 0; i < solutions2.size(); i++) {

            for (int j = 0; j < solutions1.size(); j++) {

                if (dominanceOperator.verifyDominace(solutions2.get(i).getParticleData(), solutions1.get(j).getParticleData(), problem) == Dominance.DOMINATED) {
                    nDominatedSolutions++;
                    break;
                }
            }
        }

        coverageValue = (double) nDominatedSolutions / (double) solutions2.size();
        return coverageValue;
    }

    private double calculateMinDistance(Particle solution, int solutionIndex, ArrayList<Particle> solutions) {
        double minDistance = Double.MAX_VALUE;
        double distance;

        for (int i = 0; i < solutions.size(); i++) {
            if (i != solutionIndex) {
                distance = this.calculateDistance(solution, solutions.get(i));

                if (distance < minDistance) {
                    minDistance = distance;
                }
            }
        }

        return minDistance;
    }

    private double calculateMinDistance_CSS(Particle_CSS solution, int solutionIndex, ArrayList<Particle_CSS> solutions) {
        double minDistance = Double.MAX_VALUE;
        double distance;

        for (int i = 0; i < solutions.size(); i++) {
            if (i != solutionIndex) {
                distance = this.calculateDistance_CSS(solution, solutions.get(i));

                if (distance < minDistance) {
                    minDistance = distance;
                }
            }
        }

        return minDistance;
    }

    private double calculateDistance(Particle solution, Particle anotherSolution) {
        double distance = 0.0;

        for (int i = 0; i < solution.getParticleData().getFitness().length; i++) {
            distance += Math.abs(solution.getParticleData().getFitness()[i] - anotherSolution.getParticleData().getFitness()[i]);
        }

        return distance;
    }

    private double calculateDistance_CSS(Particle_CSS solution, Particle_CSS anotherSolution) {
        double distance = 0.0;

        for (int i = 0; i < solution.getParticleData().getFitness().length; i++) {
            distance += Math.abs(solution.getParticleData().getFitness()[i] - anotherSolution.getParticleData().getFitness()[i]);
        }

        return distance;
    }

    public double calculateHypervolume(ArrayList<Particle> externalArchive, Problem problem) {
//        ArrayList<Particle> nonDominatedSwarm;
        double[] maximumValue;
        double[] minimumValue;
        double[][] normalizedFront;
        double[][] invertedFront;
        double hypervolume;

//        nonDominatedSwarm = this.nonDominated(swarm, problem);

        maximumValue = this.getMaximumValue(externalArchive, problem);
        minimumValue = this.getMinimumValue(externalArchive, problem);

        normalizedFront = this.getNormalizedFront(externalArchive, maximumValue, minimumValue, problem);
        invertedFront = this.invertedFront(normalizedFront);

        hypervolume = this.calculateHypervolumeValue(invertedFront, invertedFront.length, problem.getNObjectives());

        return hypervolume;
    }

    public double calculateHypervolume2d(ArrayList<Particle> externalArchive, Problem problem) {
        double hypervolume = 0.0;
        double tempDistance = 0.0;

        for (int i = 0; i < externalArchive.size(); i++) {
            externalArchive.get(i).getParticleData().setComparableDimension(0);
            externalArchive.get(i).getParticleData().setSortType(ParticleData.Sorting.SORTBYFITNESS);
        }

        Collections.sort(externalArchive);

        if ( problem.getName().equals("ZDT 3") ) {
            System.out.println("ZDT 3");
            for (int i = 0; i < externalArchive.size(); i++) {

                hypervolume += ( externalArchive.get(i).getParticleData().getFitness()[0] - tempDistance ) * ( externalArchive.get(i).getParticleData().getFitness()[1] + 1.0 );
                tempDistance = externalArchive.get(i).getParticleData().getFitness()[0];
            }
        }

        else {
            for (int i = 0; i < externalArchive.size(); i++) {

                hypervolume += ( externalArchive.get(i).getParticleData().getFitness()[0] - tempDistance ) * externalArchive.get(i).getParticleData().getFitness()[1];
                tempDistance = externalArchive.get(i).getParticleData().getFitness()[0];
            }
        }

        return hypervolume;
    }

    public double calculateHypervolume2d_CSS(ArrayList<Particle_CSS> externalArchive, Problem problem) {
        double hypervolume = 0.0;
        double tempDistance = 0.0;

        for (int i = 0; i < externalArchive.size(); i++) {
            externalArchive.get(i).getParticleData().setComparableDimension(0);
            externalArchive.get(i).getParticleData().setSortType(ParticleData.Sorting.SORTBYFITNESS);
        }

        Collections.sort(externalArchive);

        if ( problem.getName().equals("ZDT 3") ) {
            for (int i = 0; i < externalArchive.size(); i++) {

                hypervolume += ( externalArchive.get(i).getParticleData().getFitness()[0] - tempDistance ) * ( externalArchive.get(i).getParticleData().getFitness()[1] + 1.0 );
                tempDistance = externalArchive.get(i).getParticleData().getFitness()[0];
            }
        }

        else {
            for (int i = 0; i < externalArchive.size(); i++) {

                hypervolume += ( externalArchive.get(i).getParticleData().getFitness()[0] - tempDistance ) * externalArchive.get(i).getParticleData().getFitness()[1];
                tempDistance = externalArchive.get(i).getParticleData().getFitness()[0];
            }
        }

        return hypervolume;
    }

    double calculateGMean(ArrayList<Particle> externalArchive) {
        double gSum = 0.0;

        for (int i = 0; i < externalArchive.size(); i++) {
            gSum += externalArchive.get(i).getParticleData().getGValue();
        }

        return gSum / externalArchive.size();
    }

    private double calculateHypervolumeValue(double[][] front, int noPoints, int noObjectives) {
        int n;
        double volume, distance;

        volume = 0;
        distance = 0;
        n = noPoints;
        while (n > 0) {
            int noNondominatedPoints;
            double tempVolume, tempDistance;

            noNondominatedPoints = filterNondominatedSet(front, n, noObjectives - 1);
            tempVolume = 0;
            if (noObjectives < 3) {
                if (noNondominatedPoints < 1) {
                    System.err.println("run-time error");
                }

                tempVolume = front[0][0];
            } else {
                tempVolume = calculateHypervolumeValue(front,
                        noNondominatedPoints,
                        noObjectives - 1);
            }

            tempDistance = surfaceUnchangedTo(front, n, noObjectives - 1);
            volume += tempVolume * (tempDistance - distance);
            distance = tempDistance;
            n = reduceNondominatedSet(front, n, noObjectives - 1, distance);
        }
        return volume;
    }

    /* remove all points which have a value <= 'threshold' regarding the
    dimension 'objective'; the points referenced by
    'front[0..noPoints-1]' are considered; 'front' is resorted, such that
    'front[0..n-1]' contains the remaining points; 'n' is returned */
    int reduceNondominatedSet(double[][] front, int noPoints, int objective,
            double threshold) {
        int n;
        int i;

        n = noPoints;
        for (i = 0; i < n; i++) {
            if (front[i][objective] <= threshold) {
                n--;
                swap(front, i, n);
            }
        }

        return n;
    } // ReduceNondominatedSet

    /* all nondominated points regarding the first 'noObjectives' dimensions
    are collected; the points referenced by 'front[0..noPoints-1]' are
    considered; 'front' is resorted, such that 'front[0..n-1]' contains
    the nondominated points; n is returned */

    int filterNondominatedSet(double[][] front, int noPoints, int noObjectives) {
        int i, j;
        int n;

        n = noPoints;
        i = 0;
        while (i < n) {
            j = i + 1;
            while (j < n) {
                if (dominates(front[i], front[j], noObjectives)) {
                    /* remove point 'j' */
                    n--;
                    swap(front, j, n);
                } else if (dominates(front[j], front[i], noObjectives)) {
                    /* remove point 'i'; ensure that the point copied to index 'i'
                    is considered in the next outer loop (thus, decrement i) */
                    n--;
                    swap(front, i, n);
                    i--;
                    break;
                } else {
                    j++;
                }
            }
            i++;
        }
        return n;
    }

    /*
    returns true if 'point1' dominates 'points2' with respect to the
    to the first 'noObjectives' objectives
     */
    boolean dominates(double point1[], double point2[], int noObjectives) {
        int i;
        int betterInAnyObjective;

        betterInAnyObjective = 0;
        for (i = 0; i < noObjectives && point1[i] >= point2[i]; i++) {
            if (point1[i] > point2[i]) {
                betterInAnyObjective = 1;
            }
        }

        return ((i >= noObjectives) && (betterInAnyObjective > 0));
    } //Dominates


    void swap(double[][] front, int i, int j) {
        double[] temp;

        temp = front[i];
        front[i] = front[j];
        front[j] = temp;
    } // Swap

    /* calculate next value regarding dimension 'objective'; consider
    points referenced in 'front[0..noPoints-1]' */

    double surfaceUnchangedTo(double[][] front, int noPoints, int objective) {
        int i;
        double minValue, value;

        if (noPoints < 1) {
            System.err.println("run-time error");
        }

        minValue = front[0][objective];
        for (i = 1; i < noPoints; i++) {
            value = front[i][objective];
            if (value < minValue) {
                minValue = value;
            }
        }
        return minValue;
    } // SurfaceUnchangedTo


    private double[] getMaximumValue(ArrayList<Particle> externalArchive, Problem problem) {
        double[] maximumValue = new double[problem.getNObjectives()];
        for (int i = 0; i < problem.getNObjectives(); i++) {
            maximumValue[i] = Double.MIN_VALUE;
        }


        for (int i = 0; i < externalArchive.size(); i++) {
            for (int j = 0; j < problem.getNObjectives(); j++) {
                if (externalArchive.get(i).getParticleData().getFitness()[j] > maximumValue[j]) {
                    maximumValue[j] = externalArchive.get(i).getParticleData().getFitness()[j];
                }
            }
        }

        return maximumValue;
    }

    private double[] getMinimumValue(ArrayList<Particle> externalArchive, Problem problem) {
        double[] minimumValue = new double[problem.getNObjectives()];
        for (int i = 0; i < problem.getNObjectives(); i++) {
            minimumValue[i] = Double.MAX_VALUE;
        }


        for (int i = 0; i < externalArchive.size(); i++) {
            for (int j = 0; j < problem.getNObjectives(); j++) {
                if (externalArchive.get(i).getParticleData().getFitness()[j] < minimumValue[j]) {
                    minimumValue[j] = externalArchive.get(i).getParticleData().getFitness()[j];
                }
            }
        }

        return minimumValue;
    }

    private double[][] getNormalizedFront(ArrayList<Particle> front, double[] maximumValue, double[] minimumValue, Problem problem) {
        double[][] normalizedFront = new double[front.size()][problem.getNObjectives()];

        for (int i = 0; i < front.size(); i++) {
            for (int j = 0; j < problem.getNObjectives(); j++) {
                normalizedFront[i][j] = (front.get(i).getParticleData().getFitness()[j] - minimumValue[j]) /
                        (maximumValue[j] - minimumValue[j]);
            }
        }
        return normalizedFront;
    }

    public double distanceToClosedPoint(double[] point, double[][] front) {
        double minDistance = this.distance(point, front[0]);


        for (int i = 1; i < front.length; i++) {
            double aux = distance(point, front[i]);
            if (aux < minDistance) {
                minDistance = aux;
            }
        }

        return minDistance;
    }

    private double distance(double[] a, double[] b) {
        double distance = 0.0;

        for (int i = 0; i < a.length; i++) {
            distance += Math.pow(a[i] - b[i], 2.0);
        }
        return Math.sqrt(distance);
    }

    private double[][] invertedFront(double[][] front) {
        double[][] invertedFront = new double[front.length][];

        for (int i = 0; i < front.length; i++) {
            invertedFront[i] = new double[front[i].length];
            for (int j = 0; j < front[i].length; j++) {
                if (front[i][j] <= 1.0 && front[i][j] >= 0.0) {
                    invertedFront[i][j] = 1.0 - front[i][j];
                } else if (front[i][j] > 1.0) {
                    invertedFront[i][j] = 0.0;
                } else if (front[i][j] < 0.0) {
                    invertedFront[i][j] = 1.0;
                }
            }
        }
        return invertedFront;
    }

    private ArrayList<Particle> nonDominated(ArrayList<Particle> swarm, Problem problem) {
        ArrayList<Particle> nonDominatedSwarm;
        ArrayList<Particle> swarmClone;
        Dominance dominance;

        nonDominatedSwarm =
                new ArrayList<Particle>();
        swarmClone =
                new ArrayList<Particle>();

        for (int i = 0; i <
                swarm.size(); i++) {
            swarmClone.add((Particle) ((Particle) swarm.get(i)).clone());
        }

        swarmClone = (ArrayList<Particle>) swarm.clone();

        for (int i = 0; i <
                swarmClone.size(); i++) {

            if (!swarmClone.get(i).isDominated()) {

                for (int j = 0; j <
                        swarmClone.size(); j++) {

                    if ((swarmClone.get(i) != swarmClone.get(j)) &&
                            !swarmClone.get(j).isDominated()) {
                        dominance = dominanceOperator.verifyDominace(swarmClone.get(i), swarmClone.get(j), problem);

                        if (dominance == Dominance.DOMINATES) {
                            swarmClone.get(j).setDominated(true);
                        } else if (dominance == Dominance.DOMINATED) {
                            swarmClone.get(i).setDominated(true);
                            j =
                                    swarmClone.size();
                        }

                    }
                }
            }
        }

        for (int i = 0; i <
                swarmClone.size(); i++) {
            if (!swarmClone.get(i).isDominated()) {
                nonDominatedSwarm.add((Particle) ((Particle) swarmClone.get(i)).clone());
            }

        }

        swarmClone.clear();

        return nonDominatedSwarm;
    }
}
